/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.html.model.applet;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.ServletException;
import offset.nodes.Constants;
import offset.nodes.client.model.ServerRequest;
import offset.nodes.client.model.ServerResponse;
import offset.nodes.client.dialog.newnode.model.NewHtmlFile;
import offset.nodes.client.editor.model.messages.CancelChanges;
import offset.nodes.client.editor.model.messages.GetUUIDForPath;
import offset.nodes.client.editor.model.messages.GetVirtualInformation;
import offset.nodes.client.editor.model.messages.GetWorkflowTransitions;
import offset.nodes.client.editor.model.messages.UploadPage;
import offset.nodes.server.model.RepositoryModel;
import offset.nodes.server.model.ServerNamespaceRegistry;
import offset.nodes.server.servlet.AbstractRepositoryService;
import offset.nodes.server.servlet.Service;
import offset.nodes.server.servlet.ServiceContainer;
import offset.nodes.server.servlet.ServiceMapping;
import offset.nodes.server.workflow.model.State;
import offset.nodes.server.workflow.model.Workflow;
import offset.nodes.server.workflow.model.WorkflowModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Walter Lütgenau
 */
public class EditorServices extends AbstractRepositoryService implements ServiceContainer {

    static Logger logger = LoggerFactory.getLogger(EditorServices.class);
    ServerNamespaceRegistry namespaces = new ServerNamespaceRegistry();

    /** Creates a new instance of UploadServlet */
    public EditorServices() {
    }

    public ServiceMapping[] getServiceMappings() {
        return new ServiceMapping[]{
                    new ServiceMapping(new GetWorkflowTransitions.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getWorkflowTransitions((GetWorkflowTransitions.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetUUIDForPath.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getUUIDForPath((GetUUIDForPath.Request) request);
                }
            }, this),
                    new ServiceMapping(new CancelChanges.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return cancelChanges((CancelChanges.Request) request);
                }
            }, this),
                    new ServiceMapping(new UploadPage.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return uploadPage((UploadPage.Request) request);
                }
            }, this),
                    new ServiceMapping(new NewHtmlFile.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return newHtmlFile((NewHtmlFile.Request) request);
                }
            }, this)
                };
    }

    /**
     * Check, if the UUID passed refers to a workflow document. If true, determine the current state and the transitions
     * possible in the current state. Return the transitions.
     *
     * @param request
     * @return information about the workflow transitions
     */
    public GetWorkflowTransitions.Response getWorkflowTransitions(GetWorkflowTransitions.Request request) throws RepositoryException {
        GetWorkflowTransitions.Response response = new GetWorkflowTransitions.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node root = session.getRootNode();
        String templatePath = request.getTemplateNodePath();
        if (templatePath.startsWith("/"))
            templatePath = templatePath.substring(1);
        Node template = root.getNode(templatePath);

        Node node = template;
        Node workflowNode = null;
        Node stateNode = null;
        while (!node.isSame(root) && (workflowNode == null || stateNode == null)) {
            if (node.isNodeType(Constants.TYPE_STATE))
                stateNode = node;
            else if (node.isNodeType(Constants.TYPE_WORKFLOW))
                workflowNode = node;

            node = node.getParent();
        }

        if (workflowNode == null || stateNode == null)
            return response;

        WorkflowModel model = new WorkflowModel(session);
        Workflow workflow = model.getWorkflow(workflowNode);
        State state = workflow.getStates().get(stateNode.getName());
        String[] transitions = new String[state.getTransitionNames().size()];
        int i = 0;
        for (String transitionName : state.getTransitionNames()) {
            transitions[i++] = transitionName;
        }
        response.setTransitions(transitions);

        return response;
    }

    public CancelChanges.Response cancelChanges(CancelChanges.Request request) throws RepositoryException {
        Session session = getSession(request);
        if (session.hasPendingChanges())
            session.refresh(false);

        return new CancelChanges.Response(ServerResponse.RESULT_SUCCESS);
    }

    public UploadPage.Response uploadPage(UploadPage.Request request) throws ServletException, IOException {
        try {
            UploadPage.Response response = new UploadPage.Response(ServerResponse.RESULT_SUCCESS);

            int result = uploadPage(getSession(request),
                    request.getPath(),
                    request.getLastModified(),
                    request.getText(),
                    request.getHtml(),
                    request.getUserName(),
                    request.getComment());

            response.setResult(result);

            return response;

        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    public int uploadPage(Session session, String path, Calendar lastModified, String text, String html, String userName, String comment) throws ServletException, IOException {
        try {
            Node node = findNode(session.getRootNode(), path, Constants.TYPE_HTML_DIV);
            if (!node.isNodeType(Constants.TYPE_HTML_DIV)) { // conversion from old nt:unstructured
                Node parent = node.getParent();
                String name = node.getName();
                node.remove();
                node = parent.addNode(name, Constants.TYPE_HTML_DIV);
            }

            if (node.hasProperty(Constants.PROP_MODIFICATION_TIMESTAMP)) {
                Calendar nodeLastModified = (Calendar) node.getProperty(Constants.PROP_MODIFICATION_TIMESTAMP).getDate();
                Calendar pageLastModified = lastModified;
                if (pageLastModified.before(nodeLastModified))
                    return UploadPage.Response.RESULT_CONCURRENT_MODIFICATION;
            }

            storePage(node, text, userName, html);

            node.setProperty(Constants.PROP_CREATOR, userName);
            node.setProperty(Constants.PROP_COMMENT, comment);

            storeReferences(node, html);

            session.save();

            return ServerResponse.RESULT_SUCCESS;

        } catch (Exception e) {
            throw new ServletException(e);
        }
    }

    /**
     * Remove the node specified by path.
     *
     * @param request
     * @return the result of the removal, including the new path valid instead of the removed path.
     */
    public NewHtmlFile.Response newHtmlFile(NewHtmlFile.Request request) throws RepositoryException, ServletException, IOException {
        NewHtmlFile.Response response = new NewHtmlFile.Response(ServerResponse.RESULT_SUCCESS);


        String html = "\r\n  <h1>\n   " + request.getName() + "\n    </h1>\n ";
        String text = "     \n" + request.getName() + "\n";

        int result = uploadPage(getSession(request),
                request.getPath() + Constants.PATH_SEPARATOR + request.getName(),
                Calendar.getInstance(),
                text,
                html,
                request.getUserName(),
                "");

        response.setResult(result);

        return response;
    }

    /**
     * Return the UUID for a given path.
     *
     * @param request
     * @return
     * @throws RepositoryException
     */
    public GetUUIDForPath.Response getUUIDForPath(GetUUIDForPath.Request request) throws RepositoryException {
        GetUUIDForPath.Response response = new GetUUIDForPath.Response(ServerResponse.RESULT_SUCCESS);

        Session session = getSession(request);
        Node node = getNode(session.getRootNode(), request.getPath());
        if (node.hasProperty(Constants.JCR_UUID))
            response.setUuid(node.getProperty(Constants.JCR_UUID).getString());
        else
            response.setUuid("");

        return response;
    }


    /* interface
     ****************************************************************************************************
     * internal */
    /**
     * Return a node with a relative path to the root. Create path nodes, if necessory
     *
     * @param root
     * @param path
     * @return
     * @throws javax.jcr.RepositoryException
     */
    protected Node getNode(Node root, String path) throws RepositoryException {
        RepositoryModel repositoryModel = new RepositoryModel(root.getSession());
        return repositoryModel.getNode(root, path);
    }

    protected InputStream getFile(Session session, String path) throws RepositoryException {
        while (path.startsWith("/")) {
            path = path.substring(1);
        }

        Node root = session.getRootNode();
        Node pageNode = root.getNode(path + "/" + "jcr:content");
        if (pageNode == null)
            return null;

        InputStream input = pageNode.getProperty("jcr:data").getValue().getStream();

        return input;
    }

    protected void storePage(Node node, String text, String userName, String content) throws RepositoryException {
        if (!node.isCheckedOut())
            node.checkout();
        node.setProperty(Constants.PROP_TEXT, text);
        node.setProperty(Constants.PROP_MIME_TYPE, Constants.MIME_HTML);
        node.setProperty(Constants.PROP_LAST_MODIFIED, Calendar.getInstance());
        node.setProperty(Constants.PROP_DATA, new ByteArrayInputStream(content.getBytes()));
    }

    protected Node findNode(Node root, String path, String primaryNodeType) throws RepositoryException {
        StringTokenizer st = new StringTokenizer(path, "/");
        Node node = root;

        while (st.hasMoreTokens()) {
            String component = st.nextToken();

            Node componentNode = null;
            if (node.hasNode(component))
                componentNode = node.getNode(component);
            else if (!st.hasMoreTokens())
                componentNode = node.addNode(discardSubscription(component), primaryNodeType);
            else
                componentNode = node.addNode(component);

            node = componentNode;
        }

        return node;
    }

    protected String discardSubscription(String path) {
        return path.replaceAll("\\[.*\\]", "");
    }

    protected void storeReferences(Node node, String content) throws RepositoryException {
        String[] parts = content.split("<a href=");

        List references = new LinkedList();
        for (String part : parts) {
            int end = part.indexOf("</a>");
            if (part.indexOf("</a>") < 0)
                continue;

            part = part.substring(0, end);
            int referenceIndex = part.indexOf(Constants.PAR_NODE_REFERENCE + "=");
            if (referenceIndex < 0) // external or other reference
                continue;

            String reference = part.substring(referenceIndex + Constants.PAR_NODE_REFERENCE.length() + 1);
            String[] referenceParts = reference.split("&|\"|>");

            references.add(referenceParts[0]);
        }

        if (references.size() > 0) {
            String[] referencesArray = (String[]) references.toArray(new String[references.size()]);
            node.setProperty(Constants.PROP_HYPERLINK, referencesArray);
        } else if (node.hasProperty(Constants.PROP_HYPERLINK))
            node.getProperty(Constants.PROP_HYPERLINK).remove();
    }

}
